package ParsingSys;

import java.util.ArrayList;
import ParsingSys.Value;

public class Parser {

	private ArrayList<Value> availableValues = new ArrayList<Value>();	
	private ArrayList<Value> parsedValues = new ArrayList<Value>();	
	private ArrayList<String> matchAgainst = null;
	
	
	/**
	 * Given an array of lines from a text file,
	 * the constructor creates the respective values
	 * and their dependencies and stores them
	 * in the available values to be parsed.
	 * 
	 * Format of the lines should follow:
	 * Value ":" Dependency1 "," Dependency2 "," .. "," Dependencyn
	 * @param ArrayList<String> The lines to be parsed into values
	 */
	public Parser(ArrayList<String> lines) {
		for(String line:lines){
			line = line.replaceAll(" ", "");
 			String lineTokens[];
			lineTokens = line.split(":");
			
			String valueName = lineTokens[0];
			String valueDependencies[] = lineTokens[1].split(",");
			
			int firstColon=line.indexOf(":");
			int nextColon=line.indexOf(":", firstColon+1);
			if(nextColon!=-1) {	//the next colon is found
				System.err.println("Failed to parse value: "+valueName);
				continue;
			}
			
			if (!valueName.matches("[a-zA-Z]")){
				System.err.println("Failed to parse value: "+valueName);
				continue;
			}
			
			Value v = fetchAvailableValue(valueName);
			
			for(String dependency : valueDependencies){
				if (!dependency.matches("^(([\\s]*[a-zA-Z0-9-+@#$%^&*|<>?]*[\\s]*[,])*)([\\s]*[a-zA-Z0-9-+@#$%^&*|<>?]*[\\s]*)$")){
					System.err.println("Failed to parse "+valueName+"'s Dependency: "+dependency);
					continue;
				}
				v.addDependency(new Value(dependency));
			}
		}
	}
	
	/**
	 * Given an array of lines from a text file,
	 * the constructor creates the respective values
	 * and their dependencies and stores them
	 * in the available values to be parsed.
	 * 
	 * This function also accepts a sequence of values
	 * to be checked against the partial order.
	 * 
	 * Format of the lines should follow:
	 * Value ":" Dependency1 "," Dependency2 "," .. "," Dependencyn
	 * @param ArrayList<String> The lines to be parsed into values
	 * @param ArrayList<String> The sequence of values to be matched
	 */
	public Parser(ArrayList<String> lines, ArrayList<String> order) {
		this(lines);
		matchAgainst = order;
	}
	
	
	/**
	 * Given a list of values and their dependencies, 
	 * this function checks that all dependencies are
	 * sane and the partial order is of correct structure.
	 * e,g no loops exist.
	 * 
	 * @return ArrayList<Value>
	 */
	public ArrayList<Value> parse(){
		int index;
		
		for(Value v: availableValues)    //loop to process each value in the avilablevalues array
		{
			Value parsedValue = fetchParsedValue(v.getName());
			
			if(v.getDependencies() != null)		//process a value only when it has dependencies
			{
				for(Value dependency: v.getDependencies())	//loop to handle each dependency of the single value in avialbevalues array
				{
					parsedValue.addDependency(dependency);
					int parsedIndex = valueParsed(dependency.getName());
					
					if (parsedIndex >= 0){
						if (parsedIndex > parsedValues.indexOf(parsedValue)){
							parsedValues.remove(parsedIndex);
							index = parsedValues.indexOf(parsedValue);
							parsedValues.add(index, dependency);
						}
						
					} else {
						index = parsedValues.indexOf(parsedValue);
						parsedValues.add(index, dependency);
					}
				}
			}
		}
		return parsedValues;
		
		
	}
	
	/**
	 * Matches a sequence against an input order by checking
	 * the correct ordering of the values and their dependencies
	 * @return
	 */
	public boolean match(){
		if (matchAgainst == null) {
			System.err.println("No Values to match against, " +
					"Please use the Parse(ArrayList<String>, ArrayList<String>)" +
					" Constructor to use this function");
			System.exit(0);
		}
		//if we havent called the parse function lets call it
		if (parsedValues.size() == 0)
			parse();
		
		boolean match = true;
		int parsedIndex;
		Value parsed;
		ArrayList<String> alreadySeenCharacters = new ArrayList<String>();
		
		for (int i=0; i < matchAgainst.size(); i++){
			
			parsedIndex = valueParsed(matchAgainst.get(i));

			if (parsedIndex >= 0){
				parsed = parsedValues.get(parsedIndex);
				if (!match(parsed, alreadySeenCharacters)){
					match = false;
					break;
				}
				
			} else {
				//a value that hasnt been parsed
				//leave the function and return false
				match = false;
				break;
			}
		}
		
		return match;
				
	}
	
	/**
	 * Given a specific value, the function checks if each dependency
	 * of the value has been matched already.
	 * 
	 * If a dependency hasnt been matched, the given order is incorrect.
	 * 
	 * @param Value The value to check its dependencies
	 * @param ArrayList<String> An array of the characters we have matched and seen
	 * already.
	 * @return
	 */
	private boolean match(Value value, ArrayList<String> seenCharacters){
		
		boolean match = true;
		
		if (!value.getDependencies().isEmpty()){
			for(Value dependency : value.getDependencies()){
				//only search if its a new value we havent seen yet
				if (seenCharacters.contains(dependency.getName())){
					match = match(dependency, seenCharacters);
				} else {
					//no need to keep going, we found an error
					match = false;
					break;
				}
			}
		}
		if (match)
			seenCharacters.add(value.getName());
		
		return match;
	}
	/**
	 * Searches for a given value in the available values
	 * and returns a the matching value.
	 * 
	 * If no value is matched, a new value is created
	 * and added to the available values.
	 * 
	 * @param String Value Name to look for
	 * @return
	 */
	private Value fetchAvailableValue(String name){
		//no values have been added yet
		if (availableValues.size() == 0){
			Value v = new Value(name);
			availableValues.add(v);
			return v;
		}
		
		//search for the value if we already have a list for it
		for(Value v:availableValues){
			if (v.getName().compareTo(name) == 0)
				return v;
		}
		
		//search did not find value, we create new one
		Value v = new Value(name);
		availableValues.add(v);
		return v;
		
	}
	
	
	/**
	 * Searches for a given value in the Parsed values
	 * and returns a the matching value.
	 * 
	 * If no value is matched, a new value is created
	 * and added to the parsed values.
	 * 
	 * @param String Value Name to look for
	 * @return
	 */
	private Value fetchParsedValue(String name){
		//no values have been added yet
		if (parsedValues.size() == 0){
			Value v = new Value(name);
			parsedValues.add(v);
			return v;
		}
		
		//search for the value if we already have a list for it
		for(Value v:parsedValues){
			if (v.getName().compareTo(name) == 0)
				return v;
		}
		
		//search did not find value, we create new one
		Value v = new Value(name);
		parsedValues.add(v);
		return v;
		
	}
	
	/**
	 * Checks if a Value has been added to the parse value without
	 * creating a new one if not found.
	 * 
	 * 
	 * @param String Value Name to look for
	 * @return int Index of the Value in the Parsed array or -1 if not found
	 */
	private int valueParsed(String name){
		//search for the value if we already have a list for it
		for(int i=0; i<parsedValues.size();i++){
			if (parsedValues.get(i).getName().compareTo(name) == 0)
				return i;
		}
		
		return -1;
		
	}
}
